/**
 * 
 */
package org.justframework.ext.http;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.justframework.api.ClientConnector;
import org.justframework.api.Request;
import org.justframework.api.Response;
import org.justframework.api.rest.RestMethod;
import org.justframework.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Http client connector.
 *  
 */
public class HttpCommonClient implements ClientConnector{
	/**
	 * logger
	 */
	private static Logger logger = LoggerFactory.getLogger(HttpCommonClient.class);
	
	/**
	 * notice: 
	 * <p>1)send POST method output stream support 
	 * <p>2)send DELETE method output stream not support 
	 * <p>3)send GET method if submit params with output stream, 
	 * 	then in server side method automated changed to POST, 
	 * 	so we can't use output stream with GET method
	 * <p>4)send PUT method output stream support but in server side
	 *  can't get param with param name,so we don't use output stream with PUT method too  
	 */
	public Response rpc(Request request) {
		request.setRequestTimestamp(System.currentTimeMillis());
		String sUrl = request.getUri().toString();
		Map<String, Object> params = request.getParams();
		Map<String, Object> headers = request.getHeaders();
		String encoding = "utf-8";
		if(request.getOptions().containsKey("encoding")){
			encoding = (String)request.getOptions().get("encoding");
		}
		long connectTimeout = request.getConnectTimeout();
		long operateTimeout = request.getOperateTimeout();
		String method = request.getRequestMethod();
		
		//generate params str
		StringBuilder paramsStr = new StringBuilder();
		if(params != null){
			Set<Entry<String, Object>> entries = params.entrySet();
			for(Map.Entry entry:entries){
				String value = (entry.getValue()!=null)?(String.valueOf(entry.getValue())):"";
				paramsStr.append(entry.getKey() + "=" + value + "&");
			}
		}
		//only POST method supported submit params with OutputStream
		if(method != RestMethod.POST){
			sUrl += "?"+paramsStr.toString();
		}
		
		URL url = null;
		HttpURLConnection conn = null;
		try {
			//connect
			url = new URL(sUrl);
			conn = (HttpURLConnection) url.openConnection();
			conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			conn.setRequestMethod(method.toString());
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setConnectTimeout((int)connectTimeout);
			conn.setReadTimeout((int)operateTimeout);
			//specify headers
			conn.setRequestProperty("Accept", request.getRepresentType());
			if(headers != null && headers.size() > 0){
				Set<String> headerSet = headers.keySet();
				for(String key:headerSet){
					conn.setRequestProperty(key, String.valueOf(headers.get(key)));
				}
			}
			if(paramsStr != null && method == RestMethod.POST){
				//send request params
				BufferedWriter out = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(),encoding));
				out.write(paramsStr.toString());
				out.flush();
				out.close();
			}
			
			//receive result
			StringBuilder result = new StringBuilder();
			BufferedReader in = new BufferedReader(new InputStreamReader(conn
					.getInputStream(),encoding));
			if(in != null){
				String line = "";
				while ((line = in.readLine()) != null) {
					result.append(line);
				}
				in.close();
			}
			
			Response response = new Response();
			response.setRequestId(request.getRequestId());
			response.setRequestTimestamp(request.getRequestTimestamp());
			response.setReturnTimestamp(System.currentTimeMillis());
			response.setContent(result.toString());
			return response;
		} catch (Exception e) {
			logger.error("http rpc fail! URL:"+url+", params："+params,e);
			//deal with error stream, improved http connection reusing rate
			try {
				byte[] buf = new byte[100];
				InputStream es = conn.getErrorStream();
				if(es != null){
					while (es.read(buf) > 0) {;}
					es.close();
				}
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			throw new RuntimeException(e);
		} finally {
			//close connection
			if (conn != null){
				conn.disconnect();
			}	
		}
	}

}
